#include <coroutine>

#include "utils/CoutTabber.h"
#include "coroutines/CppReferenceStandard.h"

#define TEST_WARPPER(Namespace) {\
    LOG_HERE(#Namespace);\
    CoutTabber t;\
    Namespace::Test();\
}

namespace CppReferenceStandard {

    /// @brief
    /// A Execution Behavior 3.*
    namespace PromiseConstructor {
        FExecutionReturnObjectClass CoroutineExecution() {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution(double) {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution(std::string) {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }
        void Test() {
            {
                std::cout << "[Test FExecutionReturnObjectClass void]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution();
            }
            {
                std::cout << "[Test FExecutionReturnObjectClass double]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution(5.0);
            }
            {
                std::cout << "[Test FExecutionReturnObjectClass std::string]" << std::endl;
                CoutTabber t;
                try {
                    FExecutionReturnObjectClass _ = CoroutineExecution(std::string("TestException"));
                } catch (const char* Msg) {
                    LOG_HERE(Msg); 
                }
            }
        }   
    }

    namespace Lifetime {
        FExecutionReturnObjectClass_RetVoid CoroutineExecution_RetVoid(bool bThrowException) {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            if (bThrowException) {
                throw "Exception During Coroutine";
            }
            LOG_HERE("End  ");
        }

        FExecutionReturnObjectClass_RetInt CoroutineExecution_RetInt() {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
            co_return 0;
        }

        void Test() {
            {
                std::cout << "[Test CoroutineExecution_RetVoid ThrowException false]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass_RetVoid _ = CoroutineExecution_RetVoid(false);
            }
            {
                std::cout << "[Test CoroutineExecution_RetVoid ThrowException true]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass_RetVoid _ = CoroutineExecution_RetVoid(true);
            }
            {
                std::cout << "[Test CoroutineExecution_RetInt]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass_RetInt _ = CoroutineExecution_RetInt();
            }
        }
    }

    namespace DynamicAllocate {
        FExecutionReturnObjectClass CoroutineExecution() {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution(bool bForceError) {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution(std::string InString) {
            LOG_HERE("Begin");
            co_await std::suspend_never {};
            LOG_HERE("End  ");
        }

        void Test() {
            {
                std::cout << "[Test DynamicAllocate]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution();
            }
            {
                std::cout << "[Test DynamicAllocate no match]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution(std::string("no match new"));
            }
            {
                std::cout << "[Test DynamicAllocate bForceError false]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution(false);
            }
            {
                std::cout << "[Test DynamicAllocate bForceError true]" << std::endl;
                CoutTabber t;
                try {
                    FExecutionReturnObjectClass _ = CoroutineExecution(true);
                } catch (const char* Msg) {
                    LOG_HERE(Msg);
                }
            }
        }
    }

    namespace Awaiter {
        FExecutionReturnObjectClass CoroutineExecution_Direct() {       // co_await Behavior 1.1
            LOG_HERE("Begin");
            co_await FExecutionReturnObjectClass::FAwaiter{};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution_Indirect() {     // co_await Behavior 1.2
            LOG_HERE("Begin");
            co_await FExecutionReturnObjectClass::FAwaitable{};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution_Transform() {    // co_await Behavior 1.3
            LOG_HERE("Begin");
            co_await 5.0;
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution_Multi() {
            LOG_HERE("Begin");
            co_await FExecutionReturnObjectClass::FAwaiter{};
            LOG_HERE("Middle");
            co_await FExecutionReturnObjectClass::FAwaiter{};
            LOG_HERE("End  ");
        }
        FExecutionReturnObjectClass CoroutineExecution_AwaitReturn() {
            LOG_HERE("Begin");
            int ReturnValue = co_await FExecutionReturnObjectClass::FAwaiterReturnValue{false};
            LOG_HERE(ReturnValue);
            try {
                LOG_HERE(co_await FExecutionReturnObjectClass::FAwaiterReturnValue{true});
            } catch (const char* Msg) {
                LOG_HERE(Msg);
            }
            LOG_HERE("End  ");
        }
        void Test() {
            {
                std::cout << "[Test Awaiter Direct]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution_Direct();
            }
            {
                std::cout << "[Test Awaiter Indirect]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution_Indirect();
            }
            {
                std::cout << "[Test Awaiter Transform]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution_Transform();
            }
            {
                std::cout << "[Test Awaiter Multi]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution_Multi();
            }
            {
                std::cout << "[Test Awaiter Return Value]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass _ = CoroutineExecution_AwaitReturn();
            }
        }
    }

    namespace Yield {
        FExecutionReturnObjectClass CoroutineExecution() {       // co_await Behavior 1.1
            LOG_HERE("Begin");
            FExecutionReturnObjectClass::handle_type h = co_await FExecutionReturnObjectClass::FAwaiterGetHandle{};

            for (int i = 0; i < 10; i++) {
                if (i % 2) {
                    co_await h.promise().yield_value(i);        // same as ↓, need get promise manually
                } else {
                    co_yield i;                                 // same as ↑
                }
            }
            LOG_HERE("End  ");
        }

        void Test() {
            {
                std::cout << "[Test Yield Direct]" << std::endl;
                CoutTabber t;
                FExecutionReturnObjectClass Execution = CoroutineExecution();
                for (; Execution;) {
                    std::cout << Execution() << std::endl;
                }
            }
        }
    }

    void Test() {
        TEST_WARPPER(PromiseConstructor);
        TEST_WARPPER(Lifetime);
        TEST_WARPPER(DynamicAllocate);
        TEST_WARPPER(Awaiter);
        TEST_WARPPER(Yield);
    }
}